8.8 关键字:mutable

功能

C++编译器强制实施bitwise constness,mutable可以释放掉const成员函数中non-static变量的bitwise constness约束。

bitwise constness与logical constness

关于const成员函数有两类说法:

  • bitwise constness(或称physical constness):成员函数只有在不更改对象任何成员变量(static除外)时才可以说是const的,即不更改对象内的任何一个bit。

  • logical constness:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。

1. bitwise constness存在的问题

bitwise constness正是C++对常量性(constness)的定义,因此const成员函数不可以更改对象内任何non-static成员变量。

尽管编译器强制实施bitwise constness,但你编写程序的时候应该尽量使用“概念上的常量”。

不幸的是许多成员函数虽然不十足具备const性质但却能通过bitwise测试。更具体地说,一个更改了“指针所指物”的成员函数虽然不能算是const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const不会引发编译器异议。

举个例子,CTextBlock类返回一个reference指向对象内部值,但是却不适当地将其operator[]声明为const成员函数。不过operator[]实现代码并不更改text_ptr_,因此编译器认为它是bitwise constness并产出目标码。

class CTextBlock {
 public:
    explicit CTextBlock(const char *text) {
        text_ptr_ = new char[std::strlen(text) + 1];
        snprintf(text_ptr_, std::strlen(text) + 1, text);
    }
    ~CTextBlock() {
        delete text_ptr_;
        text_ptr_ = nullptr;
    }
    void print() const {
        std::cout << text_ptr_ << std::endl;
    }
    char& operator[](std::size_t position) const {
        return text_ptr_[position];
    }
 private:
    char* text_ptr_;
};

int main() {
    const CTextBlock foo("Cat");  // 声明一个常量
    foo.print();
    foo[0] = 'D';
    foo[1] = 'o';
    foo[2] = 'g';
    foo.print();

    return 0;
}

// 输出:
$g++ -g test.cpp -o test -std=c++11
$./test 
Cat
Dog
root

在上面的例子中,我们创建了一个常量CTextBlock且调用了它的const成员函数operator[],最终还是修改了它的值。

2. logic constness

bitwise constness这种情况导出所谓的logic constness,这一派拥护者主张一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。例如你的CTextBlock类有可能高速缓存(cache)文本区块的长度以便应付询问。

class CTextBlock {
 public:
    std::size_t length() const {
        if (!length_is_valid_) {
            text_length_ = std::strlen(text_ptr_);  // 编译不通过, 在const成员函数
            length_is_valid_ = true;                // 内不能给成员变量赋值
        }
    }

 private:
    char* text_ptr_;
    std::size_t text_length_;  // 最近一次计算的文本区块长度
    bool length_is_valid_;     // 目前的长度是否有效
};

成员函数length()并不是bitwise const的,因为text_length_length_is_valid_变量都可能被修改。这两个成员变量被修改对const CTextBlock变量而言是可以接受的,但是编译器并不接受(编译器只同意bitwise constness),这该怎么办?

3. 基于mutable实现logical constness

解决方法很简单:利用C++一个与const相关的关键字mutable(可变的),它可以释放掉non-static变量的bitwise constness约束。

class CTextBlock {
 public:
    std::size_t length() const {
        if (!length_is_valid_) {
            text_length_ = std::strlen(text_ptr_);  // 这些成员变量可能被修改, 即使
            length_is_valid_ = true;                // 在const成员函数内部
        }
    }

 private:
    char* text_ptr_;
    mutable std::size_t text_length_;  // 最近一次计算的文本区块长度
    mutable bool length_is_valid_;     // 目前的长度是否有效
};